//
// Copyright 2022 The Sparrow Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#include "sparrow/impl/format.h"

#include <ctype.h>
#include <stdarg.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

static unsigned char __hex_char_to_int(char c) {
    int retval = 0;
    if (c >= '0' && c <= '9') {
        retval = c - '0';
    } else if (c >= 'A' && c <= 'F') {
        retval = c - 'A' + 10;
    } else if (c >= 'a' && c <= 'f') {
        retval = c - 'a' + 10;
    } else {
        abort();
    }
    return (unsigned char)retval;
}

static void __hex_encode_byte(unsigned char input, char output[2], const char *alphabet) {

    char *res = output;
    char *ptr = res + 2;

    for (int i = 0; i < 2; i++) {
        *(--ptr) = alphabet[input & 0x0f];
        input >>= 4;
    }
}

static char __hex_decode_byte(const char input[2]) {

    const char *value = input;

    int res = 0;

    for (int i = 0; i < 2; i++) {
        res <<= 4;
        res += __hex_char_to_int(*value);
        value++;
    }

    return (char)res;
}

static int __hex_encode_upper(const char *input, int input_size, char *output) {
    int i = 0;

    while (i < input_size) {
        __hex_encode_byte(input[i], &output[i * 2], "0123456789ABCDEF");
        i++;
    }

    return i * 2;
}

static int __hex_encode_lower(const char *input, int input_size, char *output) {
    int i = 0;

    while (i < input_size) {
        __hex_encode_byte(input[i], &output[i * 2], "0123456789abcdef");
        i++;
    }

    return i * 2;
}

extern "C" {

struct FlexibleArray *sp_printf(const char *format, ...) {
    va_list args;
    int ret;
    size_t strp_buflen;

#ifdef _WIN32

    /* Determine the length. */
    va_start(args, format);
    ret = _vscprintf(format, args);
    va_end(args);
    if (ret < 0) {
        return NULL;
    }

    /* Allocate a new buffer, with space for the NUL terminator. */
    strp_buflen = static_cast<size_t>(ret) + 1;
    struct FlexibleArray *text = FlexibleArrayInit(strp_buflen);

    /* Print to the buffer. */
    va_start(args, format);
    vsnprintf_s(&text->data[0], strp_buflen, _TRUNCATE, format, args);

#else

    char buf[128];

    /* Use a constant-sized buffer to determine the length. */
    va_start(args, format);
    ret = vsnprintf(buf, sizeof(buf), format, args);
    va_end(args);
    if (ret < 0) {
        return NULL;
    }

    /* Return early if we have all the bytes. */
    if (static_cast<size_t>(ret) <= sizeof(buf)) {
        return FlexibleArrayInitWithData(buf, ret);
    }

    /* Allocate a new buffer, with space for the NUL terminator. */
    strp_buflen = static_cast<size_t>(ret) + 1;
    struct FlexibleArray *text = FlexibleArrayInit(strp_buflen);

    /* Try again using the larger buffer. */
    va_start(args, format);
    vsnprintf(&text->data[0], strp_buflen, format, args);

#endif

    va_end(args);
    text->length--;
    return text;
}

int sp_string_trim(const char *input, int input_size, char *output) {
    if (input == NULL || input_size <= 0 || output == NULL) {
        return 0;
    }

    const char *first = input;
    const char *end = first + input_size;
    const char *last = end - 1;

    while (first < end) {
        if (isspace(*first)) {
            first++;
        } else {
            break;
        }
    }

    if (first == end) return 0;

    while (last > first) {
        if (isspace(*last)) {
            last--;
        } else {
            break;
        }
    }

    int copy_bytes = last - first + 1;
    memcpy(output, first, copy_bytes);
    return copy_bytes;
}

#ifdef __GNUC__
#define STRTOKEN strtok_r
#else
#define STRTOKEN strtok_s
#endif

int sp_string_split(const char *target, const char *delimiters,
                    void (*callback)(const char *str, void *usr), void *usr) {

    if (target == NULL || delimiters == NULL) {
        return -1;
    }

    int count = 0;
    const char *input = target;
    char *next = NULL, *token = NULL;

    token = STRTOKEN((char *)input, delimiters, &next);
    while (token != NULL) {
        count++;
        callback(token, usr);
        token = STRTOKEN(NULL, delimiters, &next);
    }

    return count;
}
#undef STRTOKEN

int sp_hex_encode(const char *input, int input_size, char *output, int upper_case) {
    return upper_case ? __hex_encode_upper(input, input_size, output)
                      : __hex_encode_lower(input, input_size, output);
}

int sp_hex_decode(const char *input, int input_size, char *output) {
    int i = 0, count = input_size / 2;

    while (i < count) {
        output[i] = __hex_decode_byte(&input[i * 2]);
        i++;
    }

    return i;
}
} // extern "C"
